package com.hyy.lotterysystem.service.Impl;

import com.hyy.lotterysystem.common.errorcode.ServiceErrorCodeConstants;
import com.hyy.lotterysystem.common.exception.ServiceException;
import com.hyy.lotterysystem.common.utils.JacksonUtil;
import com.hyy.lotterysystem.common.utils.RedisUtil;
import com.hyy.lotterysystem.controller.param.CreateActivityParam;
import com.hyy.lotterysystem.controller.param.CreatePrizeByActivityParam;
import com.hyy.lotterysystem.controller.param.CreateUserByActivityParam;
import com.hyy.lotterysystem.controller.param.PageParam;
import com.hyy.lotterysystem.dao.dataobject.ActivityDO;
import com.hyy.lotterysystem.dao.dataobject.ActivityPrizeDO;
import com.hyy.lotterysystem.dao.dataobject.ActivityUserDO;
import com.hyy.lotterysystem.dao.dataobject.PrizeDO;
import com.hyy.lotterysystem.dao.mapper.*;
import com.hyy.lotterysystem.service.ActivityService;
import com.hyy.lotterysystem.service.dto.ActivityDTO;
import com.hyy.lotterysystem.service.dto.ActivityDetailDTO;
import com.hyy.lotterysystem.service.dto.CreateActivityDTO;
import com.hyy.lotterysystem.service.dto.PageListDTO;
import com.hyy.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.hyy.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.hyy.lotterysystem.service.enums.ActivityStatusEnum;
import com.hyy.lotterysystem.service.enums.ActivityUserStatusEnum;

import static com.hyy.lotterysystem.common.config.DelayedRabbitConfig.DELAYED_EXCHANGE_NAME;
import static com.hyy.lotterysystem.common.config.DelayedRabbitConfig.DELAYED_QUEUE_NAME;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


import java.util.*;
import java.util.stream.Collectors;

@Service
public class ActivityServiceImpl implements ActivityService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private PrizeMapper prizeMapper;

    @Autowired
    private ActivityMapper activityMapper;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private ActivityUserMapper activityUserMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    private static final Logger logger = LoggerFactory.getLogger(ActivityServiceImpl.class);


    /**
     * 活动缓存前置，为了区分业务
     */
    private final String ACTIVITY_PREFIX = "ACTIVITY_";

    /**
     * 活动缓存过期时间
     */
    private final Long ACTIVITY_TIMEOUT = 60 * 60 * 24 * 7L;


    @Override
    @Transactional(rollbackFor = Exception.class)//涉及多表，一旦失败，全部回滚
    public CreateActivityDTO createActivity(CreateActivityParam param) {
        //校验活动信息是否正确 比如奖品是否存在，人员是否存在
        checkActivityInfo(param);

        //保存活动信息
        ActivityDO activityDO = new ActivityDO();
        activityDO.setActivityName(param.getActivityName());//活动名字
        activityDO.setDescription(param.getDescription());//活动描述
        activityDO.setStatus(ActivityStatusEnum.RUNNING.name());//活动是否进行
        activityDO.setDuration(param.getDuration());//活动持续时间
        activityMapper.insert(activityDO);

        //保存关联奖品信息
        List<CreatePrizeByActivityParam> activityPrizeList = param.getActivityPrizeList();
        List<ActivityPrizeDO> activityPrizeDoList = activityPrizeList.stream()
                .map(prizeParam -> {
                    ActivityPrizeDO activityPrizeDO = new ActivityPrizeDO();
                    activityPrizeDO.setActivityId(activityDO.getId());//关联的活动id（额外加的）
                    activityPrizeDO.setPrizeId(prizeParam.getPrizeId());//奖品Id
                    activityPrizeDO.setPrizeAmount(prizeParam.getPrizeAmount());//奖品数量
                    activityPrizeDO.setPrizeTiers(prizeParam.getPrizeTiers());//奖品等级
                    activityPrizeDO.setStatus(ActivityPrizeStatusEnum.INIT.name());//奖品是否抽完（额外加的）
                    return activityPrizeDO;
                }).toList();
        activityPrizeMapper.batchInsert(activityPrizeDoList);//批量插入活动中的奖品


        //保存关联人员信息
        List<CreateUserByActivityParam> userParams = param.getActivityUserList();
        List<ActivityUserDO> activityUserDOList = userParams.stream()
                .map(userParam -> {
                    ActivityUserDO activityUserDO = new ActivityUserDO();
                    activityUserDO.setActivityId(activityDO.getId());//活动id（额外加的）
                    activityUserDO.setUserId(userParam.getUserId());//参与活动的人员id
                    activityUserDO.setUserName(userParam.getUserName());//人员姓名
                    activityUserDO.setStatus(ActivityUserStatusEnum.INIT.name());//是否抽奖（额外加的）
                    return activityUserDO;
                }).collect(Collectors.toList());
        activityUserMapper.batchInsert(activityUserDOList);

        //获取奖品基本表

        //从controller参数中，获取需要查询的奖品id
        List<Long> idList = param.getActivityPrizeList().stream()
                .map(CreatePrizeByActivityParam::getPrizeId) // 这里假设getPrizeId返回Long或long
                .distinct()
                .toList();

        //根据奖品id，把奖品信息拿出来
        List<PrizeDO> prizeDOList = prizeMapper.batchSelectByIds(idList);

        //整合存放到redis里面
        ActivityDetailDTO detailDTO = convertToActivityDetailDTO(activityDO, activityUserDOList,
                prizeDOList, activityPrizeDoList);
        cacheActivity(detailDTO);


        //判断是否是需要设定活动时间
        if (param.getDuration() != 0) {
            Map<String, Object> map = new HashMap<>();
            map.put("messageId", String.valueOf(UUID.randomUUID()));
            //消费者获取活动id就可以把活动以及活动相关连的表数据状态修改
            map.put("messageData", JacksonUtil.writeValueAsString(activityDO.getId()));

            rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_QUEUE_NAME, map);

        }


        // 构造返回
        CreateActivityDTO createActivityDTO = new CreateActivityDTO();
        createActivityDTO.setActivityId(activityDO.getId());
        return createActivityDTO;
    }

    @Override
    public PageListDTO<ActivityDTO> findActivityList(PageParam param) {
        // 获取总量
        int total = activityMapper.count();

        // 获取当前页列表
        List<ActivityDO> activityDOList = activityMapper.selectActivityList(param.offset(), param.getPageSize());
        List<ActivityDTO> activityDTOList = activityDOList.stream()
                .map(activityDO -> {
                    ActivityDTO activityDTO = new ActivityDTO();
                    activityDTO.setActivityId(activityDO.getId());//活动id
                    activityDTO.setActivityName(activityDO.getActivityName());//活动名字
                    activityDTO.setDescription(activityDO.getDescription());//活动描述
                    activityDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));//活动状态
                    activityDTO.setDuration(activityDO.getDuration());//活动持续时间
                    activityDTO.setCreateTime(activityDO.getGmtCreate());//活动创建时间

                    return activityDTO;
                }).collect(Collectors.toList());
        return new PageListDTO<>(total, activityDTOList);
    }

    @Override
    public ActivityDetailDTO getActivityDetail(Long activityId) {
        if (null == activityId) {
            logger.warn("查询活动详细信息失败，activityId为空！");
            return null;
        }
        // 查询 redis
        ActivityDetailDTO detailDTO = getActivityFromCache(activityId);
        if (null != detailDTO) {
            logger.info("查询活动详细信息成功！detailDTO={}",
                    JacksonUtil.writeValueAsString(detailDTO));
            return detailDTO;
        }

        // 如果redis不存在，查表
        // 活动表
        ActivityDO aDO = activityMapper.selectById(activityId);

        // 活动奖品表
        List<ActivityPrizeDO> apDOList = activityPrizeMapper.selectByActivityId(activityId);

        // 活动人员表
        List<ActivityUserDO> auDOList = activityUserMapper.selectByActivityId(activityId);

        // 奖品表: 先获取要查询的奖品id
        List<Long> prizeIds = apDOList.stream()
                .map(ActivityPrizeDO::getPrizeId)//获取活动奖品表，中的奖品id
                .collect(Collectors.toList());
        List<PrizeDO> pDOList = prizeMapper.batchSelectByIds(prizeIds);
        // 整合活动详细信息，存放redis
        detailDTO = convertToActivityDetailDTO(aDO, auDOList, pDOList, apDOList);
        cacheActivity(detailDTO);
        // 返回
        return detailDTO;
    }

    @Override
    public void cacheActivity(Long activityId) {
        if (null == activityId) {
            logger.warn("要缓存的活动id为空！");
            throw new ServiceException(ServiceErrorCodeConstants.CACHE_ACTIVITY_ID_IS_EMPTY);
        }

        // 查询表数据：活动表、关联奖品、关联人员、奖品信息表
        ActivityDO aDO = activityMapper.selectById(activityId);
        if (null == aDO) {
            logger.error("要缓存的活动id有误！");
            throw new ServiceException(ServiceErrorCodeConstants.CACHE_ACTIVITY_ID_ERROR);
        }
        // 活动奖品表
        List<ActivityPrizeDO> apDOList = activityPrizeMapper.selectByActivityId(activityId);
        // 活动人员表
        List<ActivityUserDO> auDOList = activityUserMapper.selectByActivityId(activityId);
        // 奖品表: 先获取要查询的奖品id
        List<Long> prizeIds = apDOList.stream()
                .map(ActivityPrizeDO::getPrizeId)
                .collect(Collectors.toList());
        List<PrizeDO> pDOList = prizeMapper.batchSelectByIds(prizeIds);
        // 整合活动详细信息，存放redis
        cacheActivity(
                convertToActivityDetailDTO(aDO, auDOList,
                        pDOList, apDOList));
    }


    /**
     * 缓存完整的活动信息 ActivityDetailDTO
     *
     * @param detailDTO
     */
    private void cacheActivity(ActivityDetailDTO detailDTO) {
        // key: ACTIVITY_12
        // value: ActivityDetailDTO(json)
        if (null == detailDTO || null == detailDTO.getActivityId()) {
            logger.warn("要缓存的整合活动信息不存在!");
            return;
        }

        //设置失败不用管，管理端在redis获取不到，会自动去表中查询
        try {
            redisUtil.set(ACTIVITY_PREFIX + detailDTO.getActivityId(),
                    JacksonUtil.writeValueAsString(detailDTO),
                    ACTIVITY_TIMEOUT);
        } catch (Exception e) {
            logger.error("缓存活动异常，ActivityDetailDTO={}",
                    JacksonUtil.writeValueAsString(detailDTO),
                    e);
        }
    }


    /**
     * 根据活动id从缓存中获取活动详细信息
     */
    private ActivityDetailDTO getActivityFromCache(Long activityId) {
        if (null == activityId) {
            logger.warn("获取缓存活动数据的activityId为空！");
            return null;
        }
        try {

            //获取redis中的活动信息：json格式
            String json = redisUtil.get(ACTIVITY_PREFIX + activityId);
            if (!StringUtils.hasText(json)) {
                logger.info("获取的缓存活动数据为空！key={}", ACTIVITY_PREFIX + activityId);
                return null;
            }
            return JacksonUtil.readValue(json, ActivityDetailDTO.class);
        } catch (Exception e) {
            logger.error("从缓存中获取活动信息异常，key={}", ACTIVITY_PREFIX + activityId, e);
            return null;
        }

    }

    /**
     * 根据基本DO整合完整的活动信息ActivityDetailDTO
     *
     * @param activityDO          活动基本信息 id 描述 状态（源）
     * @param activityUserDOList  关联活动人员信息，关联活动id 人员id 人员名字 是否抽奖
     * @param prizeDOList         奖品名字， 图片地址 、奖品价格、奖品描述（源）
     * @param activityPrizeDOList 奖品等级、奖品数量 活动id 奖品id 奖品状态
     * @return
     */
    private ActivityDetailDTO convertToActivityDetailDTO(ActivityDO activityDO,
                                                         List<ActivityUserDO> activityUserDOList,
                                                         List<PrizeDO> prizeDOList,
                                                         List<ActivityPrizeDO> activityPrizeDOList) {
        ActivityDetailDTO detailDTO = new ActivityDetailDTO();
        detailDTO.setActivityId(activityDO.getId());//活动id
        detailDTO.setActivityName(activityDO.getActivityName());//活动名字
        detailDTO.setDesc(activityDO.getDescription());//活动描述
        detailDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));//活动状态
        detailDTO.setDuration(activityDO.getDuration());//活动时间

        /*
         * 设置活动的奖品信息
         * */
        List<ActivityDetailDTO.PrizeDTO> prizeDTOList = activityPrizeDOList
                .stream()
                .map(apDO -> {
                    ActivityDetailDTO.PrizeDTO prizeDTO = new ActivityDetailDTO.PrizeDTO();
                    prizeDTO.setPrizeId(apDO.getPrizeId());


                    Optional<PrizeDO> optionalPrizeDO = prizeDOList.stream()
                            .filter(prizeDO -> prizeDO.getId().equals(apDO.getPrizeId()))
                            .findFirst();

                    /*
                     * 设置奖品源信息*/
                    // 如果PrizeDO为空，不执行当前方法，不为空才执行
                    optionalPrizeDO.ifPresent(prizeDO -> {
                        prizeDTO.setName(prizeDO.getName());
                        prizeDTO.setImageUrl(prizeDO.getImageUrl());
                        prizeDTO.setPrice(prizeDO.getPrice());
                        prizeDTO.setDescription(prizeDO.getDescription());
                    });

                    /*
                     * 设置奖品活动关联信息
                     * */
                    prizeDTO.setTiers(ActivityPrizeTiersEnum.forName(apDO.getPrizeTiers()));
                    prizeDTO.setPrizeAmount(apDO.getPrizeAmount());
                    prizeDTO.setStatus(ActivityPrizeStatusEnum.forName(apDO.getStatus()));
                    return prizeDTO;
                }).collect(Collectors.toList());
        detailDTO.setPrizeDTOList(prizeDTOList);

        /*
         * 设置活动关联的人员信息
         * */
        List<ActivityDetailDTO.UserDTO> userDTOList = activityUserDOList.stream()
                .map(auDO -> {
                    ActivityDetailDTO.UserDTO userDTO = new ActivityDetailDTO.UserDTO();
                    userDTO.setUserId(auDO.getUserId());
                    userDTO.setUserName(auDO.getUserName());
                    userDTO.setStatus(ActivityUserStatusEnum.forName(auDO.getStatus()));
                    return userDTO;
                }).collect(Collectors.toList());
        detailDTO.setUserDTOList(userDTOList);
        return detailDTO;
    }


    /*
     * 校验活动有效性*/
    private void checkActivityInfo(CreateActivityParam param) {
        if (param == null) {
            throw new ServiceException(ServiceErrorCodeConstants.CREATE_ACTIVITY_INFO_IS_NULL);
        }

        //活动创建人id不能为空
        List<Long> createUserId=new ArrayList<>();
        createUserId.add(param.getCreateUserId());
        createUserId=userMapper.selectExistByIds(createUserId);
        if(createUserId.isEmpty()){
            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_CREATE_USERID_NO_EXIST);
        }


        //人员id是否在人员表中存在
        List<Long> userIds = param.getActivityUserList().stream()
                .map(CreateUserByActivityParam::getUserId)
                .distinct()
                .toList();
        List<Long> userExistIds = userMapper.selectExistByIds(userIds);//查询数据库
        userIds.forEach(userId -> {
            if (!userExistIds.contains(userId)) {
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_USERID_NO_EXIST);
            }
        });

        //奖品id是否在奖品中存在
        List<Long> prizeIds = param.getActivityPrizeList().stream()
                .map(CreatePrizeByActivityParam::getPrizeId)
                .distinct()
                .toList();
        List<Long> prizeExistIds = prizeMapper.selectExistByIds(prizeIds);
        prizeIds.forEach(prizeId -> {
            if (!prizeExistIds.contains(prizeId)) {
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_NO_EXIST);
            }
        });

        //人员数量需要大于等于奖品数量
        Long userCount = Long.valueOf((long) param.getActivityUserList().size());
        //计算活动奖品总数量
        Long prizeCount = param.getActivityPrizeList()
                .stream()
                .mapToLong(CreatePrizeByActivityParam::getPrizeAmount)
                .sum();

        //不允许奖品数量 大于 人员数量
        if (userCount < prizeCount) {
            throw new ServiceException(ServiceErrorCodeConstants.PRIZE_AMOUNT_OUT);
        }

        // 校验活动奖品等级有效性
        param.getActivityPrizeList().forEach(prize -> {
            if (null == ActivityPrizeTiersEnum.forName(prize.getPrizeTiers())) {
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_TIERS_ERROR);
            }
        });
    }
}
